package com.zm.springcloudsentinel.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * dashboard配置时注意事项
 */
@RestController
public class SentinelController {


    //这种方式也会被dashboard检测到而且是两份，一份是自动监测到的接口名/code的资源 ， 一个是try中定义的资源名codeResource
    @RequestMapping("/code")
    public String codeResource(){
        initCodeResourceRule();//自定义限流规则 ， 测试dashboard限流规则时注释
        String msg="代码方式定义资源";
        try (Entry entry= SphU.entry("codeResource")){
            return msg;
        } catch (BlockException e) {
            return "被限流了";
        }

        /*
        另一种形式
        Entry entry = null;
        // 务必保证finally会被执行
        try {
            // 资源名可使用任意有业务语义的字符串
            entry = SphU.entry("自定义资源名");
            // 被保护的业务逻辑
            // do something...
        } catch (BlockException e1) {
            // 资源访问阻止，被限流或被降级
            // 进行相应的处理操作
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
         */
    }
    //为上面资源定义规则rule ， 使用时放在相应的方法中的第一行，会被dashboard检测到
    private static void initCodeResourceRule(){
        System.out.println("有限流规则");
        List<FlowRule> ruleList=new ArrayList<>();
        FlowRule flowRule=new FlowRule();
        flowRule.setResource("codeResource"); //资源名称
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); //限流规则类型
        flowRule.setCount(1);
        flowRule.setControlBehavior(0); //速率限制器控制行为。0. 默认（直接拒绝）， 1.热身，2.速率限制器，3。预热 + 限速器
        //更多请移步源码
        ruleList.add(flowRule);
        FlowRuleManager.loadRules(ruleList);
    }
    ///////////////////////////////////注解定义资源//////////////////////////////////////
    /*
    单配置资源名，不配置兜底方法的话可能会500，可能会返回sentinel自己的提示Blocked by Sentinel (flow limiting)
    500是因为抛出的com.alibaba.csp.sentinel.slots.block.flow.FlowException: null 异常
    在dashboard中配置规则要配置给注解定义的资源名，不然还可能会报sentinel默认的提示,也可能提示兜底方法 。 这是注解资源名和dashboard识别的资源名（接口路径）不同时；
    注解定义的资源名和dashboard定义的资源名相同时则会返回sentinel默认的提示
    不定义资源名是会识别两个资源，一个是方法的全路径 一个是接口路径 ， 定义限流方法给全路径会触发兜底，定义限流方法给接口路径不会触发兜底但是限流有效返回默认的提示
     */
    @RequestMapping("/ano")
    @SentinelResource(
            //value = "ano",
            blockHandlerClass = SentinelController.class, //限流方法所在类
            blockHandler = "anoResourceFlowException" //限流方法名称
    )
    public String anoResource(){
        return "通过注解定义的资源";
    }
    //至少有这个异常的参数，如果资源有其他参数可以又可以没有，有的话可以获取到资源的参数值，但必须有这个异常的参数
    //必须是public
    //如果该方法与资源不在同一个类中时，@SentinelResource注解中应配置blockHandlerClass属性指定本方法所在类，并且本方法必须为static
    //如果该方法与资源在同一个类中时 ， @SentinelResource注解中的blockHandlerClass属性可以不配置
    //实现兜底还可以创建一个类实现BlockExceptionHandler，重写handler方法 ， 该一场接口下有多个子类，对应不同的场景
    //FlowException 限流异常 ； ParamFlowException 热点参数异常 ； DegradeException 降级异常 ； AuthorityException 授权规则异常 ； SystemBlockException 系统规则异常
    public static String anoResourceFlowException(BlockException e){
        return "通过注解定义的资源-限流了";
    }

    @RequestMapping("/ano2")
    @SentinelResource(
            blockHandlerClass = com.zm.springcloudsentinel.handler.SentinelHandler.class
    )
    public String ano2Resource(){
        return "通过注解定义的资源";
    }

    //////////////////////////////////////////////////////////////////////
}
